package controller;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;

import model.*;
import display.*;

public class Control implements Runnable {
	/*
	 * rs type:
	 */
	public final static String MULT = "MULT";
	public final static String ADD = "ADD";
	public final static String LOAD = "LOAD";
	public final static String STOR = "STOR";

	/*
	 * op type:
	 */
	public final static String ADDD = "ADDD";
	public final static String SUBD = "SUBD";
	public final static String MULD = "MULD";
	public final static String DIVD = "DIVD";
	public final static String LD = "LD";
	public final static String ST = "ST";

	/*
	 * reg state:
	 */
	public final static String READY = "READY";

	public static int SLEEPTIME = 1000;

	Status status;
	int pc;
	int clock;
	boolean isAuto;
	SimulatorFrame simulator;

	public Control() {
		status = new Status();
		pc = 0;
		clock = 0;
		isAuto = false;
	}

	public void setSimulator(SimulatorFrame simulator) {
		this.simulator = simulator;
	}

	public int getPC() {
		return pc;
	}

	public int getClock() {
		return clock;
	}

	public int getInstructionSize() {
		return status.instructionList.size();
	}

	public void autoMode() {
		isAuto = true;
	}

	public void setTimeSlice(int time) {
		Control.SLEEPTIME = time;
	}

	public void saveMemory(String filePath) {
		BufferedWriter bw;

		try {
			bw = new BufferedWriter(new OutputStreamWriter(
					new FileOutputStream(filePath)));
			int index = 0;
			for (Memory memory : status.memoryList) {
				bw.write(index * 4 + "\t" + memory.data);
				bw.newLine();
			}
			bw.flush();
			bw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void saveIntegerReg(String filePath) {
		BufferedWriter bw;

		try {
			bw = new BufferedWriter(new OutputStreamWriter(
					new FileOutputStream(filePath)));
			int index = 0;
			for (IntegerRegister ir : status.integerRegisterList) {
				bw.write(index + "\t" + ir.data);
				bw.newLine();
			}
			bw.flush();
			bw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void manualMode() {
		isAuto = false;
	}

	public List<Instruction> getInstructionList() {
		return status.instructionList;
	}

	public List<Buffer> getLoadBufferList() {
		return status.loadBuffer;
	}

	public List<Buffer> getStoreBufferList() {
		return status.storeBuffer;
	}

	public List<Memory> getMemoryList() {
		return status.memoryList;
	}

	public List<ReservationStation> getAddRSList() {
		return status.addReservationStation;
	}

	public List<ReservationStation> getMultRSList() {
		return status.multReservationStation;
	}

	public List<FloatRegister> getFloatRegisterList() {
		return status.floatRegisterList;
	}

	public List<IntegerRegister> getIntegerRegisterList() {
		return status.integerRegisterList;
	}

	public void update() {
		System.out.println("update!");
		clock++;
		launch();
		execute();
		writeBack();
		simulator.update();
		status.printRS();
	}

	void launch() {
		if (status.instructionList.size() <= pc)
			return;
		Instruction ins = status.instructionList.get(pc);
		ReservationStation rs;
		Buffer buffer;

		switch (ins.op) {
		case ADDD:
		case SUBD:
			if ((rs = status.getFreeAddRS()) != null) {
				launchToRS(ins, rs);
				ins.isLaunch = true;
				pc++;
			}
			break;
		case MULD:
		case DIVD:
			if ((rs = status.getFreeMultRS()) != null) {
				launchToRS(ins, rs);
				ins.isLaunch = true;
				pc++;
			}
			break;
		case LD:
			if ((buffer = status.getFreeLoadBuffer()) != null) {
				launchToBuffer(ins, buffer);
				ins.isLaunch = true;
				pc++;
			}
			break;
		case ST:
			if ((buffer = status.getFreeStoreBuffer()) != null) {
				launchToBuffer(ins, buffer);
				ins.isLaunch = true;
				pc++;
			}
			break;
		default:
			break;
		}
	}

	void launchToRS(Instruction ins, ReservationStation rs) {
		rs.ins = ins;
		FloatRegister fg;
		rs.isBusy = true;
		rs.isReady = false;
		rs.op = ins.op;
		fg = status.floatRegisterList.get(ins.sourcej);
		if (fg.flag.equals(READY)) {
			rs.qi = READY;
			rs.vi = fg.data;
		} else {
			rs.qi = fg.flag;
		}
		fg = status.floatRegisterList.get(ins.sourcek);
		if (fg.flag.equals(READY)) {
			rs.qk = READY;
			rs.vk = fg.data;
		} else {
			rs.qk = fg.flag;
		}
		fg = status.floatRegisterList.get(ins.desti);
		fg.flag = rs.name;
		rs.time = -1;
	}

	void launchToBuffer(Instruction ins, Buffer buffer) {
		FloatRegister fg;

		buffer.time = -1;
		buffer.ins = ins;
		buffer.isBusy = true;
		buffer.isReady = false;
		buffer.address = ins.sourcej
				+ status.integerRegisterList.get(ins.sourcek).data;
		if (buffer.name.startsWith(LOAD)) {
			fg = status.floatRegisterList.get(ins.desti);
			fg.flag = buffer.name;
		} else {
			fg = status.floatRegisterList.get(ins.desti);
			if (fg.flag.equals(READY)) {
				buffer.vi = fg.data;
				buffer.qi = READY;
			} else {
				buffer.qi = fg.flag;
			}
		}
	}

	public void loadInstruction(String filePath) {

		System.out.println("load instruction!");

		status = new Status();
		pc = 0;
		clock = 0;

		BufferedReader br;
		String readLine;
		Instruction ins;

		try {
			br = new BufferedReader(new FileReader(filePath));
			while ((readLine = br.readLine()) != null) {
				String words[] = readLine.split("\t");
				if (words.length == 4) {
					switch (words[0]) {
					case LD:
					case ST:
						ins = new Instruction();
						ins.op = words[0];
						ins.desti = Integer.parseInt(words[1].substring(1));
						ins.sourcej = Integer.parseInt(words[2]);
						ins.sourcek = Integer.parseInt(words[3].substring(1));
						status.instructionList.add(ins);
						break;
					case ADDD:
					case SUBD:
					case MULD:
					case DIVD:
						ins = new Instruction();
						ins.op = words[0];
						ins.desti = Integer.parseInt(words[1].substring(1));
						ins.sourcej = Integer.parseInt(words[2].substring(1));
						ins.sourcek = Integer.parseInt(words[3].substring(1));
						status.instructionList.add(ins);
						break;
					default:
						break;
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		simulator.update();
	}

	public void loadMemory(String filePath) {

		System.out.println("load memory!");

		BufferedReader br;
		String readLine;

		try {
			br = new BufferedReader(new FileReader(filePath));
			while ((readLine = br.readLine()) != null) {
				String words[] = readLine.split("\t");
				if (words.length == 2) {
					try {
						int index = Integer.parseInt(words[0]);
						float value = Float.parseFloat(words[1]);
						if (index % 4 == 0 && index >= 0 && index <= 4096)
							status.memoryList.get(index / 4).data = value;
					} catch (NumberFormatException e) {
						e.printStackTrace();
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		simulator.update();
	}

	public void loadIntegerReg(String filePath) {

		System.out.println("load integer reg!");

		BufferedReader br;
		String readLine;

		try {
			br = new BufferedReader(new FileReader(filePath));
			while ((readLine = br.readLine()) != null) {
				String words[] = readLine.split("\t");
				if (words.length == 2) {
					try {
						int index = Integer.parseInt(words[0]);
						int value = Integer.parseInt(words[1]);
						if (index >= 0 && index <= 11)
							status.integerRegisterList.get(index).data = value;

					} catch (NumberFormatException e) {
						e.printStackTrace();
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		simulator.update();
	}

	void execute() {
		/*
		 * Adder:
		 */
		if (status.isAdderBusy) {
			for (ReservationStation rs : status.addReservationStation) {
				if (!rs.isBusy)
					continue;
				if (rs.time == 0) {
					rs.isReady = true;
					break;
				}
				if (rs.time > 0) {
					rs.time--;
					break;
				}
			}
		} else {
			for (ReservationStation rs : status.addReservationStation) {
				if (rs.isBusy && rs.qi.equals(READY) && rs.qk.equals(READY)) {
					status.isAdderBusy = true;
					rs.time = 2;
					rs.ins.isExecute = true;
					break;
				}
			}
		}
		/*
		 * Multer:
		 */
		if (status.isMulterBusy) {
			for (ReservationStation rs : status.multReservationStation) {
				if (!rs.isBusy)
					continue;
				if (rs.time == 0) {
					rs.isReady = true;
					break;
				}
				if (rs.time > 0) {
					rs.time--;
					break;
				}
			}
		} else {
			for (ReservationStation rs : status.multReservationStation) {
				if (rs.isBusy && rs.qi.equals(READY) && rs.qk.equals(READY)) {
					status.isMulterBusy = true;
					if (rs.op.equals(MULD)) {
						rs.time = 10;
					} else {
						rs.time = 40;
					}
					rs.ins.isExecute = true;
					break;
				}
			}
		}
		/*
		 * Load:
		 */
		if (status.isLoadBusy) {
			for (Buffer buffer : status.loadBuffer) {
				if (!buffer.isBusy)
					continue;
				if (buffer.time == 0) {
					buffer.qi = READY;
					buffer.isReady = true;
					buffer.vi = status.memoryList.get(buffer.address / 4).data;
					break;
				}
				if (buffer.time > 0) {
					buffer.time--;
					break;
				}
			}
		} else {
			Buffer buffer = status.loadBuffer.get(status.loadBufferHead);
			if (buffer.isBusy) {
				status.isLoadBusy = true;
				buffer.time = 3;
				buffer.ins.isExecute = true;
			}
		}
		/*
		 * Stor:
		 */
		if (status.isStorBusy) {
			for (Buffer buffer : status.storeBuffer) {
				if (!buffer.isBusy)
					continue;
				if (buffer.time == 0) {
					buffer.isReady = true;
					break;
				}
				if (buffer.time > 0) {
					buffer.time--;
					break;
				}
			}
		} else {
			Buffer buffer = status.storeBuffer.get(status.storeBufferHead);
			if (buffer.isBusy && buffer.qi.equals(READY)) {
				status.isStorBusy = true;
				buffer.time = 3;
				buffer.ins.isExecute = true;
			}
		}
	}

	void writeBack() {
		Float answer = 0f;
		Buffer buffer;

		buffer = status.storeBuffer.get(status.storeBufferHead);
		if (buffer.isBusy && buffer.isReady) {
			status.memoryList.get(buffer.address / 4).data = buffer.vi;
			buffer.isBusy = false;
			buffer.ins.isWriteBack = true;
			status.isStorBusy = false;
			status.storeBufferHead = (status.storeBufferHead + 1) % 3;
		}
		for (ReservationStation rs : status.addReservationStation)
			if (rs.isBusy && rs.isReady) {
				answer = opExec(rs.vi, rs.vk, rs.op);
				writeAnswer(answer, rs.name);
				rs.isBusy = false;
				rs.ins.isWriteBack = true;
				status.isAdderBusy = false;
				return;
			}
		for (ReservationStation rs : status.multReservationStation)
			if (rs.isBusy && rs.isReady) {
				answer = opExec(rs.vi, rs.vk, rs.op);
				writeAnswer(answer, rs.name);
				rs.isBusy = false;
				rs.ins.isWriteBack = true;
				status.isMulterBusy = false;
				return;
			}
		buffer = status.loadBuffer.get(status.loadBufferHead);
		if (buffer.isBusy && buffer.isReady) {
			answer = buffer.vi;
			writeAnswer(answer, buffer.name);
			buffer.isBusy = false;
			buffer.ins.isWriteBack = true;
			status.isLoadBusy = false;
			status.loadBufferHead = (status.loadBufferHead + 1) % 3;
			return;
		}
	}

	void writeAnswer(Float answer, String flag) {
		for (FloatRegister reg : status.floatRegisterList)
			if (reg.flag.equals(flag)) {
				reg.flag = READY;
				reg.data = answer;
			}
		for (ReservationStation rsx : status.addReservationStation) {
			if (rsx.qi.equals(flag)) {
				rsx.qi = READY;
				rsx.vi = answer;
			}
			if (rsx.qk.equals(flag)) {
				rsx.qk = READY;
				rsx.vk = answer;
			}
		}
		for (ReservationStation rsx : status.multReservationStation) {
			if (rsx.qi.equals(flag)) {
				rsx.qi = READY;
				rsx.vi = answer;
			}
			if (rsx.qk.equals(flag)) {
				rsx.qk = READY;
				rsx.vk = answer;
			}
		}
		for (Buffer buffer : status.storeBuffer) {
			if (buffer.qi.equals(flag)) {
				buffer.qi = READY;
				buffer.vi = answer;
			}
		}
	}

	Float opExec(Float vi, Float vj, String op) {
		Float answer = 0f;
		switch (op) {
		case ADDD:
			answer = vi + vj;
			break;
		case SUBD:
			answer = vi - vj;
			break;
		case MULD:
			answer = vi * vj;
		case DIVD:
			if (vj != 0)
				answer = vi / vj;
			break;
		default:
			break;
		}
		return answer;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while (true) {
			if (isAuto)
				update();
			try {
				Thread.sleep(SLEEPTIME);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
